package sa4

import javax.servlet.http._
import scala.xml._
import scala.collection.JavaConversions._
import scala.util.matching.Regex
import javax.servlet.ServletConfig
import scala.io.Source._
import java.io._
import org.squeryl.PrimitiveTypeMode._
import org.squeryl.KeyedEntity
import org.squeryl.annotations.OptionType
import org.squeryl.SessionFactory
import org.squeryl.Session
import org.squeryl.adapters.H2Adapter
import java.sql.Timestamp
import java.sql.Date
import scala.collection.mutable.HashMap

//////////////////////////////////////// SCHEMAS, TABLES ////////////////////////////////////////

// This class represents a book has a title and a author
class Books(val title: String, val author: String) extends KeyedEntity[Long] {
  val id = 0L
  def this() = this("", "")
}

// The database schema for our books
object Biblio extends org.squeryl.Schema {
  val books = table[Books]
}

//This class represents a football match, which has 2 teams playing, and the final score
class Match(val home: String, val away: String, val score: String) extends KeyedEntity[Long] {
  val id = 0L
  def this() = this(null, null, "")
}

// Database schema for matches
object Matches extends org.squeryl.Schema {
  val matches = table[Match]
}

//This class represents a team, which has a name
class Team(val name: String) extends KeyedEntity[Long] {
  val id = 0L
  def this() = this("")
}

// Database schema for teams
object Teams extends org.squeryl.Schema {
  val teams = table[Team]
}

////////////////////////////////////////////////////////////////////////////////

// Main class of the project
class MainFramework extends HttpServlet {
  type HandlerFunction = (HttpServletRequest, HttpServletResponse) => Unit

  override def init(config: ServletConfig) {
    super.init(config)

    Class.forName("org.h2.Driver")
    SessionFactory.concreteFactory = Some(() =>
      Session.create(
        java.sql.DriverManager.getConnection("jdbc:h2:" + System.getProperty("user.dir") + "/test"),
        new H2Adapter))

    //Recreate the databases
    transaction {
      Biblio.drop
      Biblio.create
      Matches.drop
      Matches.create
      Teams.drop
      Teams.create
    }

    // Populate the databases
    transaction {
      val Grigory = Biblio.books.insert(new Books("1984", "George Orwell"))
      val Nicolas = Biblio.books.insert(new Books("Candide", "Voltaire"))
      val Virginie = Biblio.books.insert(new Books("Whiteout", "Ken Follet"))
      val Sam = Biblio.books.insert(new Books("Dooms Days", "Sidney Sheldon"))
      val team1 = Teams.teams.insert(new Team("Colorado Avalanche"))
      val team2 = Teams.teams.insert(new Team("Dallas Stars"))
      val team3 = Teams.teams.insert(new Team("Vancouver Canucks"))
      val team4 = Teams.teams.insert(new Team("Nashville Predators"))
      val match1 = Matches.matches.insert(new Match("Colorado Avalanche", "Nashville Predators", "5-4 ot"))
      val match2 = Matches.matches.insert(new Match("Dallas Stars", "Vancouver Canucks", "3-2"))
      val match3 = Matches.matches.insert(new Match("Colorado Avalanche", "Dallas Stars", "4-6"))
    }

    // Insert new routes
    addRoute("/", welcome)
    addRoute("/htmlContents/book.css", css)
    addRoute("/htmlContents/hockey.css", cssHockey)
    addRoute("/form", makeForm)
    addRoute("/template", templateForms)
    //    addRoute("/fillTemplate", fillTemplate)
    addRoute("/bookShop", bookShop)
    addRoute("/listBooks", listBooks)
    addRoute("/matches", matchesPage)
    addRoute("/listTeams", listTeams)
  }

  //if doesnt find the path in the table error 404 not found
  override def service(req: HttpServletRequest, resp: HttpServletResponse) {

    //current context (You don`t say?!)
    currentContext() = new Context(req, resp)
    if (tableDispatch(req, resp))
      return
    resp.sendError(HttpServletResponse.SC_NOT_FOUND)
  }

  //////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////// ROUTING ////////////////////////////////////////

  //create a route and store it into a table associating it to a function
  def addRoute(route: String, funct: HandlerFunction) {
    routeTable(route) = funct
  }

  //table containing the routes
  val routeTable = new HashMap[String, HandlerFunction]

  def tableDispatch(req: HttpServletRequest, resp: HttpServletResponse): Boolean = {
    routeTable.get(req.getRequestURI) match {
      case Some(f) => f(req, resp); true
      case None => false
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////// BOOKSHOP ////////////////////////////////////////

  // Get book.css to be read whenever we display an html file linked to it
  def css(req: HttpServletRequest, resp: HttpServletResponse) = {
    val css = scala.io.Source.fromFile("htmlContents/book.css").getLines.mkString
    resp.getWriter.write(css)
    resp.setContentType("text/css")
  }

  // Creates the different forms we are using for the Bookshop application (Add and remove books)
  def bookShop(req: HttpServletRequest, resp: HttpServletResponse) {
    val formAddBook = new Form("post", "postBook(addTitle, author)")
    formAddBook.addInput("Title", "textarea", "addTitle")
    formAddBook.addInput("Author", "textarea", "author")
    formAddBook.addInput("", "submit", "Add book")
    val formRemoveBook = new Form("post", "removeBook(removeTitle)")
    formRemoveBook.addInput("Title", "textarea", "removeTitle")
    formRemoveBook.addInput("", "submit", "Remove Book")
    val ns = "<html><body>" + { formAddBook.makeForm.toString() + formRemoveBook.makeForm.toString() } + "</body></html>"
    val ns2 = XML.loadString(ns)
    val html = prepareForm(ns2)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  // Adds a book to the DB
  def postBook(title: String, author: String) = {
    transaction {
      Biblio.books.insert(new Books(title, author))
    }

    <html>
      <head>
        <title>Book added</title>
        <link rel="stylesheet" href="htmlContents/book.css"/>
      </head>
      <body>
        <h3>Book added:</h3>
        <p>{ title } as been uploaded, author is { author }</p>
        <p> <a href="/listBooks"> See all the books</a></p>
      </body>
    </html>
  }

  // Removes a book from the DB by looking for its title
  def removeBook(title: String) = {
    transaction {
      Biblio.books.deleteWhere(book => book.title === title)
    }
    <html>
      <head>
        <title>Book removed</title>
        <link rel="stylesheet" href="htmlContents/book.css"/>
      </head>
      <body>
        <h3>Book removed:</h3>
        <p>{ title } as been removed</p>
        <p> <a href="/listBooks"> See all the books</a></p>
      </body>
    </html>
  }

  // Page with the list of the books in the DB
  def listBooks(req: HttpServletRequest, resp: HttpServletResponse) {
    val xml = listPage
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  // Structure for the display of the books DB
  def info(s: Books, recurse: Boolean): NodeSeq = {
    <dl>
      <dt>Title</dt>
      <dd>
        { s.title }
      </dd>
      <dt>Author</dt>
      <dd>
        { s.author }
      </dd>
      <dt> Id </dt>
      <dd>
        { s.id }
      </dd>
    </dl>
  }

  // Page containing the list of all books in the DB
  def listPage = {
    <!--DOCTYPE html-->
    <html>
      <head>
        <title>List all members</title>
        <link rel="stylesheet" href="htmlContents/book.css"/>
      </head>
      <body>
        <h3>All Books</h3>
        <dl>
          {
            transaction {
              for (s <- Biblio.books)
                yield <dt>{ s.title }</dt>
                      <dd>
                        { info(s, false) }
                      </dd>
            }
          }
        </dl>
      </body>
    </html>
  }

  /////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////// HOCKEY MATCHES ////////////////////////////////////////

  // Get hockey.css to be read whenever we display an html file linked to it
  def cssHockey(req: HttpServletRequest, resp: HttpServletResponse) = {
    val css = scala.io.Source.fromFile("htmlContents/hockey.css").getLines.mkString
    resp.getWriter.write(css)
    resp.setContentType("text/css")
  }

  // Creates the different forms we are using for the Hockey matches application (Add teams and matches)
  def matchesPage(req: HttpServletRequest, resp: HttpServletResponse) {
    val formAddTeam = new Form("post", "postTeam(name)")
    formAddTeam.addInput("Name", "textarea", "name")
    formAddTeam.addInput("", "submit", "Add Team")
    val formAddMatch = new Form("post", "postMatch(team1, team2, score)")
    formAddMatch.addInput("Home", "textarea", "team1")
    formAddMatch.addInput("Away", "textarea", "team2")
    formAddMatch.addInput("Score", "textarea", "score")
    formAddMatch.addInput("", "submit", "Add Match")
    val ns = "<html><body>" + { formAddTeam.makeForm.toString() + formAddMatch.makeForm.toString() } + "</body></html>"
    val ns2 = XML.loadString(ns)
    val html = prepareForm(ns2)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  // Adds a team to the DB
  def postTeam(name: String) = {
    println(name)
    transaction {
      Teams.teams.insert(new Team(name))
    }
    <html>
      <head>
        <title>Team added</title>
        <link rel="stylesheet" href="htmlContents/hockey.css"/>
      </head>
      <body>
        <h3>Team added:</h3>
        <p>{ name } has been added</p>
        <p><a href="/listTeams"> See all the teams and matches</a></p>
      </body>
    </html>
  }

  // Adds a match to the DB
  def postMatch(home: String, away: String, score: String) = {
    transaction {
      Matches.matches.insert(new Match(home, away, score))
    }
    <html>
      <head>
        <title>Match added</title>
      </head>
      <body>
        <h3>Match added:</h3>
        <p>{ home } vs { away } has been added with a score of { score }</p>
        <p><a href="/listTeams"> See all the teams and matches</a></p>
      </body>
    </html>
  }

  // Page with the list of the teams and matches in the DB
  def listTeams(req: HttpServletRequest, resp: HttpServletResponse) {
    val xml = pageTeams
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  // Structure for the display of the teams DB
  def infoTeam(s: Team, recurse: Boolean): NodeSeq = {
    <dl>
      <dt>Name</dt>
      <dd>
        { s.name }
      </dd>
      <dt> Id </dt>
      <dd>
        { s.id }
      </dd>
    </dl>
  }

  // Structure for the display of the matches DB
  def infoMatch(m: Match, recurse: Boolean): NodeSeq = {
    <dl>
      <dt>Home Team</dt>
      <dd>
        { m.home }
      </dd>
      <dt>Away Team</dt>
      <dd>
        { m.away }
      </dd>
      <dt>Score</dt>
      <dd>
        { m.score }
      </dd>
      <dt> Id </dt>
      <dd>
        { m.id }
      </dd>
    </dl>
  }

  // Page containing the list of all teams and matches in the DB
  def pageTeams = {
    <html>
      <head>
        <title>List all members</title>
        <link rel="stylesheet" href="htmlContents/hockey.css"/>
      </head>
      <body>
        <h3>All Teams</h3>
        <dl>
          {
            transaction {
              for (s <- Teams.teams)
                yield <dt>{ s.name }</dt>
                      <dd>
                        { infoTeam(s, false) }
                      </dd>
            }
          }
        </dl>
        <hr/>
        <h3>All Matches</h3>
        <dl>
          {
            transaction {

              for (m <- Matches.matches)
                yield <dt>{ m.home + " vs " + m.away }</dt>
                      <dd>
                        { infoMatch(m, false) }
                      </dd>
            }
          }
        </dl>
      </body>
    </html>
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////// FUNCTIONS CORRESPONDING TO ROUTES ////////////////////////////////////////

  //welcome page function
  def welcome(req: HttpServletRequest, resp: HttpServletResponse) {
    val xml = renderPageWelcome
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  //creates the form in order to fill the templates
  def templateForms(req: HttpServletRequest, resp: HttpServletResponse) {
    val xml = renderPageTemplateForms
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  def fillTemplateLogin(a: String, file: String) = {
    passTemplate(a, file)
    val xml = XML.loadFile("htmlContents/content2.html")
    val html = processTemplate(xml)
    html
  }

  def fillTemplateMain(a: String, file: String) = {
    passTemplate(a, "main")
    val xml = XML.loadFile("htmlContents/content2.html")
    val html = processTemplate(xml)
    html
  }

  //fills template with the string passed to passTemplate
  //  def fillTemplate(req: HttpServletRequest, resp: HttpServletResponse) {
  //    passTemplate("hello title")
  //    val xml2 = XML.loadFile("htmlContents/content2.html")
  //    val xml = XML.loadFile("htmlContents/content1.html")
  //    val html = processTemplate(xml2)
  //    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  //  }

  ///////////////////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////////////// FORM /////////////////////////////////////////

  /**
   * Answers to the http request sending the form
   * @param req Request from the servlet
   * @param resp Response to the servlet
   */
  def makeForm(req: HttpServletRequest, resp: HttpServletResponse) {
    val html = prepareForm(renderPage)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  /**
   * *
   * Prepare the form
   * @param template The template of the form we are using
   * @return NodeSeq The template changed
   */
  def prepareForm(template: NodeSeq): NodeSeq = {
    import scala.xml.transform._
    import scala.xml.Null // hide scala.Null
    object MakeForm extends RewriteRule {
      override def transform(n: Node): Seq[Node] = n match {
        case form: Elem if form.label == "form" =>
          val Some(handler) = form.attribute("handler")
          var Some(method) = form.attribute("method")

          // Make the handler and install in routeTable
          val (uri, f) = makeHandler(handler.text)

          addRoute(uri, f)
          // Add an action to the form.
          form % Attribute(None, "action", Text(uri), Null)
        case n => n
      }
    }

    template.map(ns => new RuleTransformer(MakeForm)(ns))
  }

  /**
   * *
   * Creates a new path with its corresponding function
   * @param s
   * @return String A new path
   * @return HandlerFunction The corresponding function
   */
  def makeHandler(s: String): (String, HandlerFunction) = {
    // Parse the handler string.
    val q = "^(\\w+)\\((.*)\\)$".r

    val (methodName, argNames) = s match {
      case q(methodName, argNames) => (methodName, argNames.split(",").map(_.trim))
      case _ => throw new RuntimeException("bad handler: " + s)
    }

    // Look for a method that takes the right number of strings.
    val method = this.getClass.getMethod(methodName, argNames.map(_ => classOf[String]): _*)
    val h = (req: HttpServletRequest, resp: HttpServletResponse) => {
      val result = method.invoke(this, argNames.map(k => req.getParameter(k)): _*)
      result match {
        case s: String =>
          resp.getWriter.write(s)
        case html: NodeSeq =>
          resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
      }
    }
    (handlerURI(methodName), h)
  }

  /**
   * *
   * Constructs a new path with the method name
   * @param methodName The method that will be used as path
   */
  def handlerURI(methodName: String) = {
    "/" + methodName
  }

  ///////////////////////////////////////////////////////////////////////////
  ///////////////////////RENDER PAGES FROM ROUTING//////////////////////////

  //it overrides the file called content2.html with the new string passed to the function.
  //it will then replace all the dive with the id= here with the given input.
  //it takes as starting template the file called template.html
  def passTemplate(a: String, file: String) {

    var f = new File("htmlContents/content2.html")
    val input = "<surround with='" + file + ".html' at='here'>" + a + "</surround>"
    var fstream = new FileWriter("htmlContents/content2.html")
    var out = new BufferedWriter(fstream)
    out.write(input)
    out.close()
    // FileUtils.writeStringToFile(new File("htmlContents/content1.html"), "<surround with='template.html' at='here'>"+input+"</surround>")  
  }

  //it should render a post form
  def renderForm(func: HandlerFunction, text: String, value: String) = {
    var sessionId = currentContext().session.getId()
    println(func)
    val function = "" + func + ""
    <html>
      <head>
        <title>Load Template</title>
      </head>
      <body>
        <h3>Fill template</h3>
        <form method="post" handler={ function }>
          <p>
            <span>Body text: </span>
            <textarea name={ text }></textarea>
          </p>
          <p>
            <input type="submit" value={ value }/>
          </p>
        </form>
        <p>Session: { sessionId }</p>
      </body>
    </html>
  }

  def renderPage = {
    var sessionId = currentContext().session.getId()
    <html>
      <head>
        <title>Form example</title>
      </head>
      <body>
        <h3>Post a comment</h3>
        <form method="post" handler="postComment(author, content)">
          <p>
            <span>Your name: </span>
            <input type="text" name="author"/>
          </p>
          <p>
            <span>Your message: </span>
            <textarea name="content"></textarea>
          </p>
          <p>
            <input type="submit" value="Submit your comment"/>
          </p>
        </form>
        <p>Session: { sessionId }</p>
      </body>
    </html>
  }

  //generates the page for the form of the template
  def renderPageTemplateForms = {
    var sessionId = currentContext().session.getId()
    <html>
      <head>
        <title>Load Template</title>
      </head>
      <body>
        <h3>Fill template</h3>
        <form method="post" handler="fillTemplate3(a)">
          <p>
            <span>Body text: </span>
            <textarea name="a"></textarea>
          </p>
          <p>
            <input type="submit" value="Fill your template"/>
          </p>
        </form>
        <p>Session: { sessionId }</p>
      </body>
    </html>
  }

  //try
  val sessionMap = new HashMap[String, String]
  object Count extends SessionVar[Int](0)
  object RequestCount extends RequestVar[Int](0)
  val basicUser = new BasicUser("greg", "123", "1")
  object user extends SessionVar[BasicUser](basicUser)

  //generates the welcome page
  def renderPageWelcome = {
    Count() = Count() + 1
    Count.setIntervalSession(35)

    //    Count.finishSession
    val sessionid = Count.getIdSession
    val username = user().getUserName()
    //RequestCount() = RequestCount() + 1
    //var sessionId = session.getId()
    //RequestCount.alter(_ + 1)
    <html>
      <head>
        <title>Welcome</title>
      </head>
      <body>
        <h1>Welcome to the WebFramework</h1>
        <p>Run your code at <a href="/fillTemplate">fill template.</a></p>
        <p>Or go to <a href="/template">template</a> and give the string you want to have on your page.</p>
        <p>You can try our <a href="/bookShop">Bookshop application</a>, or see the content of our <a href="/listBooks">book database</a></p>
        <p> Or you can try <a href="/matches"> Hockey matches application </a>, or even see the <a href="/listTeams"> list of all teams and matches </a></p>
        <p>Session: { sessionid }.</p>
        <p>Username: { username }.</p>
      </body>
    </html>
  }

  def postComment(author: String, content: String) = {
    var sessionId = currentContext().session.getId()
    <html>
      <head>
        <title>Form submitted</title>
      </head>
      <body>
        <h3>Form submitted</h3>
        <p>{ author } submitted the following comment:</p>
        <blockquote>{ content }</blockquote>
        <p>Session: { sessionId }</p>
      </body>
    </html>
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////PROCESSES////////////////////////////////////////////////////////////////////

  def processTemplate(template: NodeSeq): NodeSeq = {
    import scala.xml.transform._
    import scala.xml.Null // hide scala.Null

    //take the div with the id we are looking for
    class ProcessWrapper(ns: NodeSeq, where: String) extends RewriteRule {
      override def transform(n: Node): Seq[Node] = n match {
        case elem: Elem if elem.label == "div" =>
          if (elem.attribute("id").toString() == "Some(" + where + ")") ns
          else n
        case n => n
      }
    }

    object ProcessSurround extends RewriteRule {
      override def transform(n: Node): Seq[Node] = n match {
        case elem: Elem if elem.label == "surround" =>
          val fileName = (elem \\ "@with").text
          val where = (elem \\ "@at").text
          // In a real framework, this would be a file name and we'd load the XML from a file,
          // but here we'll just get some hard-coded XML.
          val wrapperNode = XML.loadFile("templates/" + fileName)

          val wrappedNode = elem.child
          wrapperNode.map(ns => new RuleTransformer(new ProcessWrapper(wrappedNode, where))(ns))
        case n => n
      }
    }

    template.map(ns => new RuleTransformer(ProcessSurround)(ns))
  }

  ///////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////// SESSION ////////////////////////////////////

  // The current context. Each request has its own thread, so the context needs to be
  // thread local.
  var currentContext = new ThreadLocal[Context](null)

  // A mutable variable whose lifetime is a session.
  class SessionVar[A](default: A) extends KeyedVar[A](default) {
    def update(x: A) = currentContext().session.setAttribute(key, x)
    protected def get = currentContext().session.getAttribute(key)
    def getIdSession: String = currentContext().session.getId()
    def setIntervalSession(i: Int) = currentContext().session.setMaxInactiveInterval(i)
    def getIntervalSession: Int = currentContext().session.getMaxInactiveInterval()
    def finishSession = currentContext().session.invalidate()
  }

  // A mutable variable whose lifetime is a single request.
  class RequestVar[A](default: A) extends KeyedVar[A](default) {
    def update(x: A) = currentContext().request.setAttribute(key, x)
    protected def get = currentContext().request.getAttribute(key)
  }

  ///////////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////// PERSISTANCE ////////////////////////////////////

  def removeStudent(data: Books) =
    Biblio.books.deleteWhere(stuToBeDeleted => stuToBeDeleted.id === data.id)

  def UpDateAuthor(toBeUpdated: String, new_name: String) =
    update(Biblio.books)(s => where(s.author === toBeUpdated)
      set (s.author := new_name))

  def UpDateTitle(toBeUpdated: String, new_name: String) =
    update(Biblio.books)(s => where(s.title === toBeUpdated)
      set (s.title := new_name))

  def write(resp: HttpServletResponse, ns: NodeSeq) = resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(ns))

  def logIn(name: String, surname: String, password: String): scala.xml.Elem = {
    val sessionid = Count.getIdSession
    Count.setIntervalSession(65)

    transaction {
      var f: scala.xml.Elem = null
      for (s <- Students.students) {
        if (s.name == name && s.surname == surname && s.password == password) {
          val usr = "" + name + " " + surname + " "
          sessionMap += sessionid -> usr
          f = XML.loadFile("htmlContents/loggedin.html")
        }
      }
      if (f == null) {
        f = XML.loadFile("htmlContents/errorLoggedin.html")
      }
      return f
    }
  }

  def formEnter(funct: String): Form = {
    val formEnter = new Form("post", "" + funct + "(name, surname, password)")
    formEnter.addInput("Name", "textarea", "name")
    formEnter.addInput("Surname", "textarea", "surname")
    formEnter.addInput("Password", "textarea", "password")
    formEnter.addInput("", "submit", "Log In")
    return formEnter
  }

  def register(name: String, surname: String, password: String): scala.xml.Elem = {
    transaction {
      var f: scala.xml.Elem = null
      if (name != null && surname != null && password != null) {
        Students.students.insert(new Student(0, name, surname, password, 0, 0, 0, 0, 0))
        f = XML.loadFile("htmlContents/registered.html")
      }
      if (f == null) {
        f = XML.loadFile("htmlContents/notRegistered.html")
      }
      return f
    }
  }

}
